module nade.vector;

import std.math;
import std.conv;

import derelict.opengl.gl;


template MOp2(string N, string OP) {
	mixin(
			`
			TVector2 ` ~ N ~ `(R)(const R rhs) const
			{
				static if(is(R == struct))
					return TVector2(cast(T)(x ` ~ OP ~ ` rhs.x), cast(T)(y ` ~ OP ~ ` rhs.y));
				else
					return TVector2(cast(T)(x ` ~ OP ~ ` rhs), cast(T)(y ` ~ OP ~ ` rhs));
			}
			TVector2 ` ~ N ~ `Assign(R)(const R rhs)
			{
				static if(is(R == struct))
				{
					x ` ~ OP ~ `= rhs.x;
					y ` ~ OP ~ `= rhs.y;
				}
				else
				{
					x ` ~ OP ~ `= rhs;
					y ` ~ OP ~ `= rhs;
				}
				return this;
			}
			`
		);
}


template MOp3(string N, string OP) {
	mixin(
			`
			TVector3 ` ~ N ~ `(R)(const R rhs) const
			{
				static if(is(R == struct))
					return TVector3(cast(T)(x ` ~ OP ~ ` rhs.x), cast(T)(y ` ~ OP ~ ` rhs.y), cast(T)(z ` ~ OP ~ ` rhs.z));
				else
					return TVector3(cast(T)(x ` ~ OP ~ ` rhs), cast(T)(y ` ~ OP ~ ` rhs), cast(T)(z ` ~ OP ~ ` rhs));
			}
			TVector3 ` ~ N ~ `Assign(R)(const R rhs)
			{
				static if(is(R == struct))
				{
					x ` ~ OP ~ `= rhs.x;
					y ` ~ OP ~ `= rhs.y;
					z ` ~ OP ~ `= rhs.z;
				}
				else
				{
					x ` ~ OP ~ `= rhs;
					y ` ~ OP ~ `= rhs;
					z ` ~ OP ~ `= rhs;
				}
				return this;
			}
			`
		);
}


struct TVector2(T = real) {
	T x = 0, y = 0;

	static TVector2 opCall(T x, T y)
	{
		TVector2 r;
		r.x = x;
		r.y = y;
		return r;
	}
	static TVector2 opCall(const TVector2 v)
	{
		TVector2 r;
		r.x = v.x;
		r.y = v.y;
		return r;
	}
	static TVector2 fromPolar(real angle, real dist)
	{
		return TVector2(cast(T)(cos(angle)*dist), cast(T)(sin(angle)*dist));
	}

	TVector2 opAssign(const TVector2 rhs) { x = rhs.x;	y = rhs.y; return this; }

	TVector2 opNeg() const { return TVector2(-x, -y); }

	mixin MOp2!("opAdd", "+");
	mixin MOp2!("opSub", "-");
	mixin MOp2!("opMul", "*");
	mixin MOp2!("opDiv", "/");

	R as(R)() const
	{
		static assert(is(R == struct));
		return R(x, y);
	}

	double modulus2() const { return cast(double)(x*x + y*y); }
	double modulus() const { return cast(double)sqrt(modulus2()); }
	double modulusEstimate() const
	{
		const ax = (x < 0) ? -x : x;
		const ay = (y < 0) ? -y : y;
		return cast(double)((ax > ay) ? (ax + ay*0.5) : (ax*0.5 + ay));
	}
	T orthoModulus() const
	{
		static if(is(T : int))
			return cast(T)(abs(cast(int)x) + abs(cast(int)y));
		else
			return cast(T)(abs(cast(double)x) + abs(cast(double)y));
	}
	T diagonalModulus() const
	{
		static if(is(T : int))
		{
			const ax = abs(cast(int)x);
			const ay = abs(cast(int)y);
			return cast(T)((ax > ay) ? ax : ay);
		}
		else
		{
			const ax = abs(cast(double)x);
			const ay = abs(cast(double)y);
			return cast(T)((ax > ay) ? ax : ay);
		}
	}

	TVector2 dup() const { return TVector2(x, y); }
	TVector2 inverse() const { return TVector2(-x, -y); }
	TVector2 inverseX() const { return TVector2(-x, y); }
	TVector2 inverseY() const { return TVector2(x, -y); }
	TVector2 left() const { return TVector2(-y, x); }
	TVector2 right() const { return TVector2(y, -x); }
	TVector2 normal() const { return opMul(1.0/modulus()); }
	TVector2 normal(real length) const { return opMul(length/modulus()); }

	T dot(const TVector2 rhs) const { return cast(T)(x*rhs.x + y*rhs.y); }
	T cross(const TVector2 rhs) const { return cast(T)(cast(T)(x*rhs.y - y*rhs.x)); }
	TVector2 cross(T rhs) const { return TVector2(cast(T)(y*rhs), cast(T)(-x*rhs)); }

	bool isLeft(const TVector2 rhs) const { return (x*rhs.y - y*rhs.x) > 0; }
	bool isRight(const TVector2 rhs) const { return (y*rhs.x - x*rhs.y) > 0; }

	// TVector2 rotated(real radians) const
	// {
		// const c = cos(radians);
		// const s = sin(radians);

		// return TVector2(cast(T)(x*c - y*s), cast(T)(x*s + y*c));
	// }

	TVector2 min(const TVector2 rhs) const
	{
		return TVector2(
			cast(T)((x < rhs.x) ? x : rhs.x),
			cast(T)((y < rhs.y) ? y : rhs.y)
			);
	}
	TVector2 max(const TVector2 rhs) const
	{
		return TVector2(
			cast(T)((x > rhs.x) ? x : rhs.x),
			cast(T)((y > rhs.y) ? y : rhs.y)
			);
	}

	void render() const
	{
		glVertex2f(x, y);
	}
	void translate() const
	{
		glTranslatef(x, y, 0);
	}
	void scale() const
	{
		glScalef(x, y, 1);
	}

	bool isZero() const
	{
		static if(is(T : long))
			return x == 0  &&  y == 0;
		else
			return x >= -T.epsilon  &&  x <= T.epsilon
				&&  y >= -T.epsilon  &&  y <= T.epsilon
				;
	}

	// TVector2 xy() const { return TVector2(x, y); }
	// TVector2 yx() const { return TVector2(y, x); }

	// string toString() const { return '(' ~ to!(string)(x) ~ ' ' ~ to!(string)(y) ~ ')'; }
	// T[] toArray(T)()
	// {
		// T[] r = new T[2];
		// r[0] = x;
		// r[1] = y;
		// return r;
	// }

	static const ZERO = TVector2(cast(T)0, cast(T)0);
	static const AXIS_POS_X = TVector2(cast(T)1, cast(T)0);
	static const AXIS_POS_Y = TVector2(cast(T)0, cast(T)1);
	static const AXIS_NEG_X = TVector2(cast(T)-1, cast(T)0);
	static const AXIS_NEG_Y = TVector2(cast(T)0, cast(T)-1);
	static alias AXIS_POS_X AXIS_X;
	static alias AXIS_POS_Y AXIS_Y;
}

alias TVector2!(byte) Vector2b;
alias TVector2!(ubyte) Vector2ub;
alias TVector2!(short) Vector2s;
alias TVector2!(ushort) Vector2us;
alias TVector2!(int) Vector2i;
alias TVector2!(uint) Vector2ui;
alias TVector2!(long) Vector2l;
alias TVector2!(ulong) Vector2ul;
alias TVector2!(float) Vector2f;
alias TVector2!(real) Vector2r;
alias TVector2!(double) Vector2d;
alias Vector2r Vector2;


struct TVector3(T = real) {
	T x = 0, y = 0, z = 0;

	TVector3 opAssign(R)(const R rhs)
	{
		static assert(is(R == struct));
		x = rhs.x;	y = rhs.y;	z = rhs.z;
		return this;
	}

	TVector3 opNeg() const { return TVector3(-x, -y, -z); }

	mixin MOp3!("opAdd", "+");
	mixin MOp3!("opSub", "-");
	mixin MOp3!("opMul", "*");
	mixin MOp3!("opDiv", "/");

	R as(R)() const
	{
		static assert(is(R == struct));
		return R(x, y, z);
	}

	double modulus2() const { return cast(double)(x*x + y*y + z*z); }
	double modulus() const { return cast(double)sqrt(modulus2()); }
	T orthogonalModulus() const
	{
		static if(is(T : int))
			return cast(T)(abs(cast(int)x) + abs(cast(int)y) + abs(cast(int)z));
		else
			return cast(T)(abs(cast(double)x) + abs(cast(double)y) + abs(cast(double)z));
	}
	T diagonalModulus() const
	{
		static if(is(T : int))
		{
			const ax = abs(cast(int)x);
			const ay = abs(cast(int)y);
			const az = abs(cast(int)z);
			return cast(T)((ax > ay) ? ((ax > az) ? ax : az) : (ay > az) ? ay : az);
		}
		else
		{
			const ax = abs(cast(double)x);
			const ay = abs(cast(double)y);
			const az = abs(cast(double)z);
			return cast(T)((ax > ay) ? ((ax > az) ? ax : az) : (ay > az) ? ay : az);
		}
	}

	TVector3 dup() const { return TVector3(x, y, z); }
	TVector3 inverse() const { return TVector3(-x, -y, -z); }
	TVector3 normal() const { return opMul(1.0/modulus()); }

	T dot(const TVector2!(T) rhs) const { return cast(T)(x*rhs.x + y*rhs.y); }
	T dot(const TVector3 rhs) const { return cast(T)(x*rhs.x + y*rhs.y + z*rhs.z); }
	TVector3 cross(R)(const R rhs) const
	{
		static assert(is(R == struct));
		return TVector3(y*rhs.z - z*rhs.y, z*rhs.x - x*rhs.z, x*rhs.y - y*rhs.x);
	}

	TVector3 min(const TVector3 rhs) const
	{
		return TVector3(
			cast(T)((x < rhs.x) ? x : rhs.x),
			cast(T)((y < rhs.y) ? y : rhs.y),
			cast(T)((z < rhs.z) ? z : rhs.z)
			);
	}
	TVector3 max(const TVector3 rhs) const
	{
		return TVector3(
			cast(T)((x > rhs.x) ? x : rhs.x),
			cast(T)((y > rhs.y) ? y : rhs.y),
			cast(T)((z > rhs.z) ? z : rhs.z)
			);
	}

	// void render() const
	// {
		// glVertex3f(x, y, z);
	// }
	// void sendTranslation() const
	// {
		// glTranslatef(x, y, z);
	// }
	// void sendScale() const
	// {
		// glScalef(x, y, z);
	// }

	// bool isZero() const
	// {
		// static if(is(T : long))
			// return x == 0  &&  y == 0  &&  z == 0;
		// else
			// return x >= -T.epsilon  &&  x <= T.epsilon
				// &&  y >= -T.epsilon  &&  y <= T.epsilon
				// &&  z >= -T.epsilon  &&  z <= T.epsilon
				// ;
	// }

	// Vector2 xy() const { return Vector2(x, y); }
	// Vector2 xz() const { return Vector2(x, z); }
	// Vector2 yx() const { return Vector2(y, x); }
	// Vector2 yz() const { return Vector2(y, z); }
	// Vector2 zx() const { return Vector2(z, x); }
	// Vector2 zy() const { return Vector2(z, y); }
	// TVector3 xyz() const { return TVector3(x, y, z); }
	// TVector3 xzy() const { return TVector3(x, z, y); }
	// TVector3 yxz() const { return TVector3(y, x, z); }
	// TVector3 yzx() const { return TVector3(y, z, x); }
	// TVector3 zxy() const { return TVector3(z, x, y); }
	// TVector3 zyx() const { return TVector3(z, y, x); }
	
	// string toString() { return '(' ~ to!(string)(x) ~ ' ' ~ to!(string)(y) ~ ' ' ~ to!(string)(z) ~ ')'; }
	// T[] toArray(T)() const
	// {
		// T[] r = new T[3];
		// r[0] = x;
		// r[1] = y;
		// r[2] = z;
		// return r;
	// }

	static const ZERO = TVector3(cast(T)0, cast(T)0, cast(T)0);
	static const AXIS_POS_X = TVector3(cast(T)1, cast(T)0, cast(T)0);
	static const AXIS_POS_Y = TVector3(cast(T)0, cast(T)1, cast(T)0);
	static const AXIS_POS_Z = TVector3(cast(T)0, cast(T)0, cast(T)1);
	static const AXIS_NEG_X = TVector3(cast(T)-1, cast(T)0, cast(T)0);
	static const AXIS_NEG_Y = TVector3(cast(T)0, cast(T)-1, cast(T)0);
	static const AXIS_NEG_Z = TVector3(cast(T)0, cast(T)0, cast(T)-1);
	static alias AXIS_POS_X AXIS_X;
	static alias AXIS_POS_Y AXIS_Y;
	static alias AXIS_POS_Z AXIS_Z;
}

alias TVector3!(byte) Vector3b;
alias TVector3!(ubyte) Vector3ub;
alias TVector3!(short) Vector3s;
alias TVector3!(ushort) Vector3us;
alias TVector3!(int) Vector3i;
alias TVector3!(uint) Vector3ui;
alias TVector3!(long) Vector3l;
alias TVector3!(ulong) Vector3ul;
alias TVector3!(float) Vector3f;
alias TVector3!(real) Vector3r;
alias TVector3!(double) Vector3d;
alias Vector3r Vector3;
